package com.ipan.poi.excel.hander;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ipan.poi.PoiConfig;
import com.ipan.poi.excel.XlsObjectFactory;
import com.ipan.poi.excel.config.XlsEntity;
import com.ipan.poi.excel.config.XlsProperty;
import com.ipan.poi.excel.exception.XlsParserException;
import com.ipan.poi.excel.importer.XlsResult;
import com.ipan.poi.excel.service.XlsService;
import com.ipan.poi.excel.util.XlsUtils;
import com.ipan.poi.utils.PoiBeanHelper;
import com.ipan.poi.utils.PoiDateHelper;

/**
 * 默认XLS处理器
 * 1 以Excel导入文件的列不变为核心，支持多个Sheet的导入；
 * 2 允许出现一条记录对应多个实体类的情况；
 * 		2.1 Excel的字段与实体类字段是1:1对应；
 * 			可以将多个实体类对象的字段封装到一个对象内，然后重载save方法，将一个对象拆成多个实体类，然后保存至数据库；
 * 			如果冗余字段不多或者在前台显示也同样需要的情况下，可以直接在实体类中通过@Transient注释来接受冗余字段，然后进行自行转换；
 * 		2.2 Excel的字段与实体类字段是1:多对应；
 * 			重载setValue方法，自行处理特定字段至实体类字段；特殊类型无法转换的情况也可以通过重载setValue方法来实现；
 * 
 * 创建：使用工厂类XlsObjectFactory创建，可以被多线程共享；
 * 注意：Excel文件第一列必须是非空的列，程序会根据第一列的行数来统计有效行数；
 * 注意：Excel文件中获取数字类型是double，如果对应的实体类类型是字符串，那么解析出来的字符串有可能多了小数点跟小数位，这跟预期不一致；
 * 介意要么将Excel单元格改成文本类型或者调整实体类类型与Excel保持一致或者自己继承BaseXlsHander对特殊字段特殊处理；
 * 
 * @author iPan
 * @version 2013-09-15
 */
public class BaseXlsHander implements XlsHander {
	protected XlsService<Object> service = null;
	protected ThreadLocal<Object> entity = new ThreadLocal<Object>();
	protected Logger logger = LoggerFactory.getLogger(getClass());

	public BaseXlsHander() {
		this.service = XlsObjectFactory.getDefaultXlsService();
	}
	
	public BaseXlsHander(XlsService<Object> service) {
		this.service = service;
	}
	
	public void init() {}
	
	// 验证记录是否重复；若记录存在，会将当前线程的记录保存起来；
	// true：记录存在  false：不记录存在
	public boolean isExists(XlsEntity defEntity, Object formBean) {
		if (!PoiConfig.getInstance().isXlsImporterValidate()) {
			return false;
		}
		
		List<Object> list = doFind(defEntity, formBean);
		boolean result = false;
		if (list == null || list.size() == 0) {
			result = false;
		} else if (list.size() == 1) {
			result = true;
			setEntity(list.get(0));
		} else if (list.size() > 1) {
			result = true;
			throw new RuntimeException("验证记录出错：验证条件不唯一！");
		}
		return result;
	}
	
	// 子类可以重写该方法，用于验证记录是否存在；
	protected List<Object> doFind(XlsEntity defEntity, Object formBean) {
		List<Object> result = service.selectXls(defEntity, formBean);
		return result;
	}
	
	// 在做validate以后，如果需要将重复记录的实体调出，可以做setEntity；然后使用getEntity来获取；
	protected Object getEntity() {
		return entity.get();
	}

	protected void setEntity(Object obj) {
		entity.set(obj);
	}
	
	protected void removeEntity() {
		entity.remove();
	}

	public void parser(XlsEntity defEntity, Row row, Object formBean)  throws XlsParserException {
		// 获取实际列数
		int len = row.getLastCellNum();
		logger.debug("总列数：{}", len);
		for (int i = 0; i < len; ++i) {
			logger.debug("当前列：{}", i);
			// 表格单元格
			Cell cell = row.getCell(i);
			// 实体字段
			XlsProperty property = defEntity.getProperty(i);
			// 是否需要解析
			if (!property.isEnable()) {
				continue;
			}
			// 设置字段值
			try {
				setValue(cell, property, formBean);
			} catch (Exception e) {
				XlsParserException exception = new XlsParserException("为类[" + formBean.getClass().getName() + "]设置属性出错！", e);
				exception.setErrorCode(i);
				throw exception;
			}
		}
	}

	public void save(XlsEntity defEntity, Object formBean, XlsResult result) throws Exception {
		// 如果配置文件ipan.poi.xlsImporter.validate=false，则不会进行验证，直接返回false；
		boolean exists = isExists(defEntity, formBean);
		// 存在记录，执行更新；
		if (exists) {
			Object entity = this.getEntity();
			update(defEntity, formBean, entity);
			result.addUpdateCount();
			
		// 执行插入；
		} else {
			insert(defEntity, formBean);
			result.addInsertCount();
		}
	}
	
	// 子类可以覆盖该方法；
	public void insert(XlsEntity defEntity, Object formBean) {
		service.insertXls(defEntity, formBean);
	}
	
	// 子类可以覆盖该方法；
	/** 
	 * 更新记录
	 * 读取 Excel的记录保存在 formBean中，原先数据库记录在entityBean中；
	 * 默认是对导入配置文件配置的所有字段进行更新；
	 * 可以通过重载formBeanToEntityBean来设置要更新的字段；
	 * 这样实现主要考虑到项目中一般都使用ORM框架来操作数据库，
	 * 比如Hibernate，如果自行实现XlsService接口，那么查询的时候entityBean会被Hibernate框架管理，我们就的使用entityBean来操作了；
	 */
	public void update(XlsEntity defEntity, Object formBean, Object entityBean) {
		formBeanToEntityBean(defEntity, formBean, entityBean);
		service.updateXls(defEntity, entityBean);
	}
	
	public XlsService<Object> getService() {
		return service;
	}

	public void setService(XlsService<Object> service) {
		this.service = service;
	}

	public void saveAsFile(File importFile) {
	}
	
	// 提交的记录设置到数据库已有的记录中
	protected void formBeanToEntityBean(XlsEntity defEntity, Object formBean, Object entityBean) {
		List<XlsProperty> proList = defEntity.getUnValidProperty();
		if (proList == null || proList.size() < 1) {
			return;
		}
		for (XlsProperty pro : proList) {
			String name = pro.getName();
			Object value = PoiBeanHelper.getBeanValue(formBean, name);
			PoiBeanHelper.setBeanValueOfStrict(entityBean, name, value);
		}
	}
	
	// 子类可以覆盖该方法，用于自定义设值；
	protected void setValue(Cell cell, XlsProperty field, Object formBean) throws Exception {
		Object value = getCellValue(cell);
		if (value == null) {
			return ;
		}
		String fieldName = field.getName();
		Class<?> targetType = getFieldType(formBean, fieldName);
		value = convertType(value, field, targetType);
		callSetMethod(formBean, fieldName, value);
	}

	protected void callSetMethod(Object entity, String name, Object arg) throws Exception {
		PoiBeanHelper.setBeanValueOfAuto(entity, name, arg);
	}
	
	protected Object convertType(Object value, XlsProperty field, Class<?> targetType) throws Exception {
		if (targetType == BigDecimal.class) {
			value = (value != null) ? new BigDecimal(value.toString()) : null; 
			
		} else if (targetType == Date.class) {
			if (value != null && !(value instanceof Date)) {
				String strDate = value.toString();
				// 只处理单个时间转换
				if (!PoiDateHelper.isMutiDate(strDate)) {
					String pattern = field.getPattern();
					if (StringUtils.isNotEmpty(pattern)) {
						try {
							value = PoiDateHelper.getDate(strDate, pattern);
						} catch (Exception e) {
							value = PoiDateHelper.fuzzyParseDate(strDate);
						}
					} else {
						value = PoiDateHelper.fuzzyParseDate(strDate);
					}
					if (value == null) {
						throw new Exception("字段：[" + targetType.getName() + "]，时间格式无法转换！");
					}
				}
			}
		}
		
		return (value != null) ? ConvertUtils.convert(value, targetType) : null;
	}

	protected Class<?> getFieldType(Object entity, String fieldName) throws Exception {
		return entity.getClass().getDeclaredField(fieldName).getType();
	}
	
	protected Object getCellValue(Cell cell) {
		Object value = XlsUtils.getCellValue(cell);
		return value;
	}

	protected void copyFile(File srcDir, File destDir) {
		try {
			FileUtils.copyFile(srcDir, destDir);
		} catch (IOException e) {
			throw new RuntimeException("保存导入文件出错！", e);
		}
	}
}
